package org.datascooter.parser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import org.datascooter.bundle.BundleFactory;
import org.datascooter.bundle.CrossTable;
import org.datascooter.bundle.EntityBundle;
import org.datascooter.bundle.EntityPart;
import org.datascooter.bundle.attribute.DBIndex;
import org.datascooter.bundle.attribute.PersistAttribute;
import org.datascooter.bundle.attribute.PersistChildAttribute;
import org.datascooter.bundle.attribute.PersistReferenceAttribute;
import org.datascooter.bundle.attribute.PersistencePolicy;
import org.datascooter.bundle.attribute.Reference;
import org.datascooter.exception.EntityNotMappedException;
import org.datascooter.generator.ContextIdGenerator;
import org.datascooter.generator.IntIncrementIdGenerator;
import org.datascooter.generator.LongIncrementIdGenerator;
import org.datascooter.generator.UUIDGenerator;
import org.datascooter.impl.Condition;
import org.datascooter.impl.LogicValue;
import org.datascooter.inface.IBundleProvider;
import org.datascooter.inface.IIdGenerator;
import org.datascooter.inface.IXMLReader;
import org.datascooter.utils.DBType;
import org.datascooter.utils.LangUtils;

/**
 * Purposed for parsing object-relational mapping from eclipse extension point org.datascooter.mapping
 * 
 * @author nemo
 * 
 */
public class DSMappingProvider extends AbstractProvider<EntityBundle> implements IBundleProvider {

	private static final String CROSS_REFERENCE_ATTRIBUTE = "cross_reference_attribute";
	private static final String JOIN_FROM_CHILD = "join_from_child";
	private static final String JOIN_ATTRIBUTE = "join_attribute";
	private static final String CROSS_ENTITY = "cross_entity";
	private static final String COLUMN = "column";
	private static final String UPDATABLE2 = "updatable";
	private static final String NULLABLE2 = "nullable";
	private static final String ORDER = "order";
	private static final String VALUE_HOOK = "value_hook";
	private static final String VALUE = "value";
	private static final String CONSTRAINT = "constraint";
	private static final String ATTRIBUTE_VALUE = "attribute_value";
	private static final String FALSE = "false";
	private static final String INSERTABLE2 = "insertable";
	private static final String PRECISION2 = "precision";
	private static final String SCALE2 = "scale";
	private static final String TYPE2 = "type";
	private static final String QUERY2 = "query";
	private static final String TRUE = "true";
	private static final String EMBEDDED2 = "embedded";
	private static final String POLICY = "policy";
	private static final String ATTRIBUTE2 = "attribute";
	private static final String CONTEXT_GENERATOR = "context_generator";
	private static final String EMBEDDABLE = "embeddable";
	private static final String CONTAINER_ATTRIBUTE = "container_attribute";
	private static final String REFERENCE2 = "reference";
	private static final String PERSISTABLE_CHILD = "persistable_child";
	private static final String ENTITY_PART = "entity_part";
	private static final String INDEX2 = "index";
	private static final String LOGIC_CONDITION = "logic_condition";
	private static final String DISCRIMINATOR = "discriminator";
	private static final String BACK_REFERENCE_ATTRIBUTE = "back_reference_attribute";
	private static final String REFERENCE_ATTRIBUTE = "reference_attribute";
	private static final String SUFFIX = "suffix";
	private static final String CLASS = "class";
	private static final String TABLE = "table";
	private static final String ID = "id";
	private static final String NAME = "name";
	private static final String DB_SCHEMA = "db_schema";
	private static final String SIMPLE_ATTRIBUTE = "simple_attribute";
	private static final String PREFIX = "prefix";
	private static final String REALM = "realm";
	private static final String CROSS_TABLE = "cross_table";
	private static final String ENTITY2 = "entity";
	private static final String STANDART_ATTRIBUTES = "standart_attributes";
	private List<EntityBundle> bundleList = new ArrayList<EntityBundle>();
	private List<CrossTable> crossList = new ArrayList<CrossTable>();
	private Map<String, EntityBundle> bundleMap = new HashMap<String, EntityBundle>();
	private List<EntityBundle> schemaList = new ArrayList<EntityBundle>();

	public DSMappingProvider(IXMLReader... readers) {
		super(readers);
	}

	@Override
	protected void processRoot(IXMLReader element) throws EntityNotMappedException, SecurityException, NoSuchMethodException {
		Logger.getLogger(this.getClass().getName()).info("***Process schema:  " + element.getAttribute(NAME));
		EntityBundle schemaBundle = new EntityBundle(element.getAttribute(NAME), LangUtils.isBooleanString(element.getAttribute(DB_SCHEMA), false));
		if (bundleMap.put(schemaBundle.entity, schemaBundle) == null) {
			schemaList.add(schemaBundle);
		}
		if (element.getChildren(STANDART_ATTRIBUTES).length > 0)
			processStandartAttributes(schemaBundle, element.getChildren(STANDART_ATTRIBUTES)[0]);
		for (IXMLReader entity : element.getChildren(ENTITY2)) {
			processEntity(schemaBundle, entity, false);
		}
		for (IXMLReader entity : element.getChildren(CROSS_ENTITY)) {
			processCrossEntity(schemaBundle, entity, false);
		}
		for (IXMLReader child : element.getChildren(CROSS_TABLE)) {
			processCross(schemaBundle, child);
		}
		for (IXMLReader child : element.getChildren(REALM)) {
			processRealm(schemaBundle, child);
		}
	}

	private void processCross(EntityBundle parent, IXMLReader table) {
		String containerClass = table.getAttribute("container");
		String containerAttribute = table.getAttribute("container_attribute");
		String contentClass = table.getAttribute("content");
		String tableName = table.getAttribute("table_name");
		String mapKey = table.getAttribute("map_key");
		crossList.add(new CrossTable(containerClass, containerAttribute, contentClass, tableName, mapKey));
	}

	private void processStandartAttributes(EntityBundle schemaBundle, IXMLReader element) throws EntityNotMappedException, SecurityException, NoSuchMethodException {
		schemaBundle.setColumnPrefix(element.getAttribute(PREFIX));
		processid(element.getChildren(ID)[0], schemaBundle);
		schemaBundle.setIdGenerator(processIdGenerator(element.getChildren(ID)[0]));
		for (IXMLReader attribute : element.getChildren(SIMPLE_ATTRIBUTE)) {
			processSimpleAttribute(attribute, schemaBundle);
		}
	}

	@SuppressWarnings("nls")
	private void processRealm(EntityBundle parent, IXMLReader realm) throws EntityNotMappedException, SecurityException, NoSuchMethodException {
		parent.setTablePrefix(realm.getAttribute(PREFIX));
		parent.setSuffix(realm.getAttribute(SUFFIX));
		for (IXMLReader entity : realm.getChildren(ENTITY2)) {
			processEntity(parent, entity, false);
		}
		for (IXMLReader entity : realm.getChildren(CROSS_ENTITY)) {
			processCrossEntity(parent, entity, false);
		}
		for (IXMLReader child : realm.getChildren(CROSS_TABLE)) {
			processCross(parent, child);
		}
		for (IXMLReader child : realm.getChildren(REALM)) {
			processRealm(parent, child);
		}
	}

	private EntityBundle processCrossEntity(EntityBundle parent, IXMLReader entity, boolean isPart) throws EntityNotMappedException, SecurityException, NoSuchMethodException {
		EntityBundle processEntity = processEntity(parent, entity, false);
		processEntity.cross = true;
		return processEntity;
	}

	@SuppressWarnings("nls")
	private EntityBundle processEntity(EntityBundle parent, IXMLReader entity, boolean isPart) throws EntityNotMappedException, SecurityException, NoSuchMethodException {
		EntityBundle bundle = null;
		if (isPart) {
			bundle = parent.createPart(entity.getAttribute(NAME), entity.getAttribute(TABLE));
		} else {
			bundle = parent.createChild(entity.getAttribute(NAME), entity.getAttribute(TABLE), entity.getAttribute(CLASS), LangUtils.isBooleanString(entity.getAttribute("uncheckable"), false));
		}
		if (bundleMap.put(bundle.entity, bundle) == null) {
			bundleList.add(bundle);
		}
		for (IXMLReader attribute : entity.getChildren(SIMPLE_ATTRIBUTE)) {
			processSimpleAttribute(attribute, bundle);
		}
		for (IXMLReader reference : entity.getChildren(REFERENCE_ATTRIBUTE)) {
			processReferenceAttribute(parent, reference, bundle);
		}
		for (IXMLReader backReference : entity.getChildren(BACK_REFERENCE_ATTRIBUTE)) {
			bundle.addBackReference(backReference.getAttribute(NAME), backReference.getAttribute(ENTITY2), backReference.getAttribute(REFERENCE_ATTRIBUTE));
		}
		for (IXMLReader reference : entity.getChildren(CROSS_REFERENCE_ATTRIBUTE)) {
			bundle.addCrossReference(
				reference.getAttribute(NAME),
				reference.getAttribute(ENTITY2),
				reference.getAttribute(CROSS_ENTITY),
				reference.getAttribute(JOIN_ATTRIBUTE),
				reference.getAttribute(JOIN_FROM_CHILD));
		}
		for (IXMLReader attribute : entity.getChildren(DISCRIMINATOR)) {
			for (IXMLReader condition : attribute.getChildren(LOGIC_CONDITION)) {
				bundle.setDiscriminator(processLogic(condition));
			}
		}
		for (IXMLReader index : entity.getChildren(INDEX2)) {
			processIndex(bundle, index);
		}
		for (IXMLReader child : entity.getChildren(ENTITY2)) {
			processEntity(bundle, child, false);
		}
		for (IXMLReader child : entity.getChildren(CROSS_ENTITY)) {
			processCrossEntity(bundle, child, false);
		}
		for (IXMLReader part : entity.getChildren(ENTITY_PART)) {
			processParts(bundle, part);
		}
		for (IXMLReader child : entity.getChildren(PERSISTABLE_CHILD)) {
			processPersistedChild(bundle, child);
		}
		for (IXMLReader reference : entity.getChildren(REFERENCE2)) {
			processReference(bundle, reference);
		}
		for (IXMLReader containerAttribute : entity.getChildren(CONTAINER_ATTRIBUTE)) {
			processContainerAttribute(bundle, containerAttribute);
		}
		for (IXMLReader containerAttribute : entity.getChildren(EMBEDDABLE)) {
			processContainerAttribute(bundle, containerAttribute);
		}
		for (IXMLReader reader : entity.getChildren(CONTEXT_GENERATOR)) {
			List<PersistAttribute> attributes = new ArrayList<PersistAttribute>();
			for (IXMLReader attr : reader.getChildren(ATTRIBUTE2)) {
				String attribute = attr.getAttribute(NAME);
				if (attribute != null) {
					attributes.add(bundle.getAttribute(attribute));
				}
			}
			bundle.setIdGenerator(new ContextIdGenerator(attributes));
		}
		return bundle;
	}

	private void processParts(EntityBundle bundle, IXMLReader part) throws EntityNotMappedException, SecurityException, NoSuchMethodException {
		String entityAttr = part.getAttribute(ENTITY2);
		String klass = part.getAttribute(CLASS);
		String joinFromChildStr = part.getAttribute(JOIN_FROM_CHILD);
		boolean joinFromChild = ((joinFromChildStr != null) && (joinFromChildStr.equalsIgnoreCase(TRUE))) ? true : false;
		IXMLReader[] entityArr = part.getChildren(ENTITY2);
		if (entityArr.length > 0) {
			IXMLReader entity = entityArr[0];
			EntityBundle entityBundle = processEntity(bundle, entity, true);
			bundle.addPart(new EntityPart(entityBundle.entity, part.getAttribute(JOIN_ATTRIBUTE), joinFromChild));
		} else {
			String entity = LangUtils.isEmptyString(entityAttr) ? klass : entityAttr;
			bundle.addPart(new EntityPart(entity, part.getAttribute(JOIN_ATTRIBUTE), joinFromChild));
		}
	}

	private void processContainerAttribute(EntityBundle bundle, IXMLReader containerAttribute) throws SecurityException, NoSuchMethodException {
		String name = containerAttribute.getAttribute(NAME);
		String entity = containerAttribute.getAttribute(ENTITY2);
		String klass = containerAttribute.getAttribute(CLASS);
		String policyStr = containerAttribute.getAttribute(POLICY);
		String embeddedStr = containerAttribute.getAttribute(EMBEDDED2);
		boolean embedded = ((embeddedStr != null) && (embeddedStr.equals(TRUE))) ? true : false;
		bundle.addContainerAttribute(BundleFactory.createContainer(
			name,
			(LangUtils.isEmptyString(entity) ? klass : entity),
			containerAttribute.getAttribute("map_key_attribute"),
			PersistencePolicy.valueOf(policyStr),
			embedded,
			false));
	}

	private void processPersistedChild(EntityBundle parent, IXMLReader child) throws EntityNotMappedException, SecurityException, NoSuchMethodException {
		String name = child.getAttribute(NAME);
		String klass = child.getAttribute(CLASS);
		String joinFromChildStr = child.getAttribute(JOIN_FROM_CHILD);
		boolean joinFromChild = ((joinFromChildStr != null) && (joinFromChildStr.equalsIgnoreCase(TRUE))) ? true : false;
		// String entity = LangUtils.isEmptyString(name) ? klass : name;
		PersistChildAttribute persistChildAttribute = BundleFactory.createPersistChild(
			name,
			klass,
			child.getAttribute("map_key_attribute"),
			PersistencePolicy.valueOf(child.getAttribute(POLICY)),
			false,
			false,
			child.getAttribute(JOIN_ATTRIBUTE),
			joinFromChild);
		parent.addPersistableChild(persistChildAttribute);
		for (IXMLReader query : child.getChildren(QUERY2)) {
			persistChildAttribute.setQuery(new DSQueryProvider(query).processQuery(query, null, null, null));
		}
		for (IXMLReader logicCondition : child.getChildren(LOGIC_CONDITION)) {
			persistChildAttribute.setCondition(processLogic(logicCondition));
		}
	}

	private void processReference(EntityBundle bundle, IXMLReader referElement) throws EntityNotMappedException, SecurityException, NoSuchMethodException {
		Reference reference = new Reference(referElement.getAttribute(NAME));
		for (IXMLReader list : referElement.getChildren("list")) {
			for (IXMLReader query : list.getChildren(QUERY2)) {
				reference.setList(new DSQueryProvider(query).processQuery(query, null, null, null));
				bundle.addReference(reference);
				return;
			}
		}
		for (IXMLReader set : referElement.getChildren("set")) {
			for (IXMLReader query : set.getChildren(QUERY2)) {
				reference.setSet(new DSQueryProvider(query).processQuery(query, null, null, null));
				bundle.addReference(reference);
				return;
			}
		}
		for (IXMLReader map : referElement.getChildren("map")) {
			for (IXMLReader query : map.getChildren(QUERY2)) {
				reference.setMap(new DSQueryProvider(query).processQuery(query, null, null, null), map.getAttribute("key_attribute"));
				bundle.addReference(reference);
				return;
			}
		}
	}

	private void processIndex(EntityBundle bundle, IXMLReader index) {
		DBIndex dbIndex = new DBIndex(index.getAttribute(NAME), index.getAttribute(TYPE2));
		bundle.addIndex(dbIndex);
		for (IXMLReader attribute : index.getChildren("indexed_attibute")) {
			dbIndex.addAttribute(bundle.getAttribute(attribute.getAttribute(NAME)));
		}
	}

	@SuppressWarnings("nls")
	private void processid(IXMLReader attribute, EntityBundle bundle) throws SecurityException, NoSuchMethodException {
		String db_type = attribute.getAttribute(TYPE2);
		DBType type = db_type == null ? DBType.STRING : DBType.valueOf(db_type);
		String scale = attribute.getAttribute(SCALE2);
		String precision = attribute.getAttribute(PRECISION2);
		int scaleL = scale == null ? 0 : Integer.valueOf(scale);
		int precisionL = precision == null ? 0 : Integer.valueOf(precision);
		String insertableStr = attribute.getAttribute(INSERTABLE2);
		boolean insertable = ((insertableStr != null) && (insertableStr.equals(FALSE))) ? false : true;
		String updatableStr = attribute.getAttribute(UPDATABLE2);
		boolean updatable = ((updatableStr != null) && (updatableStr.equals(FALSE))) ? false : true;
		bundle.setId(BundleFactory.createId(attribute.getAttribute(NAME), attribute.getAttribute(COLUMN), type, scaleL, precisionL, insertable, updatable, false));
	}

	private IIdGenerator<?> processIdGenerator(IXMLReader attribute) throws EntityNotMappedException {
		String db_type = attribute.getAttribute(TYPE2);
		DBType type = db_type == null ? DBType.STRING : DBType.valueOf(db_type);
		for (IXMLReader reader : attribute.getChildren("incremental_generator")) {
			switch (type) {
				case LONG:
					return new LongIncrementIdGenerator(Long.valueOf(reader.getAttribute("seed")), Long.valueOf(reader.getAttribute("increment")));
				case INT:
					return new IntIncrementIdGenerator(Integer.valueOf(reader.getAttribute("seed")), Integer.valueOf(reader.getAttribute("increment")));
			}
		}
		switch (type) {
			case LONG:
				return new LongIncrementIdGenerator();
			case INT:
				return new IntIncrementIdGenerator();
		}
		return new UUIDGenerator();
	}

	@SuppressWarnings("nls")
	private void processSimpleAttribute(IXMLReader attribute, EntityBundle bundle) throws SecurityException, NoSuchMethodException {
		String db_type = attribute.getAttribute(TYPE2);
		String scale = attribute.getAttribute(SCALE2);
		String precision = attribute.getAttribute(PRECISION2);
		String nullableStr = attribute.getAttribute(NULLABLE2);
		boolean nullable = ((nullableStr != null) && (nullableStr.equals(FALSE))) ? Boolean.FALSE : Boolean.TRUE;
		String insertableStr = attribute.getAttribute(INSERTABLE2);
		boolean insertable = ((insertableStr != null) && (insertableStr.equals(FALSE))) ? Boolean.FALSE : Boolean.TRUE;
		String updatableStr = attribute.getAttribute(UPDATABLE2);
		boolean updatable = ((updatableStr != null) && (updatableStr.equals(FALSE))) ? Boolean.FALSE : Boolean.TRUE;
		int scaleL = scale == null ? 0 : Integer.valueOf(scale);
		int precisionL = precision == null ? 0 : Integer.valueOf(precision);
		DBType type = null;
		if (db_type == null) {
			type = DBType.STRING;
		} else {
			type = DBType.valueOf(db_type);
		}
		try {
			bundle.addPersistAttribute(BundleFactory.createPersist(attribute.getAttribute(NAME), attribute.getAttribute(COLUMN), type, scaleL, precisionL, insertable, updatable, nullable));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@SuppressWarnings("nls")
	private Condition processLogic(IXMLReader logic) {
		List<Condition> childLogicList = new ArrayList<Condition>();
		for (IXMLReader childLogic : logic.getChildren(LOGIC_CONDITION)) {
			childLogicList.add(processLogic(childLogic));
		}
		Set<LogicValue> set = new HashSet<LogicValue>();
		for (IXMLReader attributeValue : logic.getChildren(ATTRIBUTE_VALUE)) {
			LogicValue logicValue = new LogicValue(attributeValue.getAttribute(ATTRIBUTE2), attributeValue.getAttribute(CONSTRAINT), attributeValue.getAttribute(VALUE));
			set.add(logicValue);
			for (IXMLReader valueHook : logic.getChildren(VALUE_HOOK)) {
				logicValue.setHook(valueHook.getAttribute(ENTITY2), valueHook.getAttribute(ATTRIBUTE2), valueHook.getAttribute(ORDER));
			}
		}
		return new Condition(logic.getAttribute("operation"), childLogicList, new ArrayList<LogicValue>(set));
	}

	@SuppressWarnings("nls")
	private void processReferenceAttribute(EntityBundle parent, IXMLReader attribute, EntityBundle bundle) throws SecurityException, NoSuchMethodException {
		String nullableStr = attribute.getAttribute(NULLABLE2);
		boolean nullable = ((nullableStr != null) && (nullableStr.equals(FALSE))) ? false : true;
		String insertableStr = attribute.getAttribute(INSERTABLE2);
		boolean insertable = ((insertableStr != null) && (insertableStr.equals(FALSE))) ? false : true;
		String updatableStr = attribute.getAttribute(UPDATABLE2);
		boolean updatable = ((updatableStr != null) && (updatableStr.equals(FALSE))) ? false : true;
		PersistReferenceAttribute createReference = BundleFactory.createReference(
			attribute.getAttribute(NAME),
			attribute.getAttribute(ENTITY2),
			attribute.getAttribute(COLUMN),
			null,
			0,
			0,
			nullable,
			insertable,
			updatable);
		bundle.addReferenceAttribute(createReference);
		bundle.addIndex(new DBIndex(attribute.getAttribute(COLUMN), null, createReference));
	}

	@Override
	public EntityBundle getBundle(Object obj) throws EntityNotMappedException {
		return null;
	}

	@Override
	public List<EntityBundle> provideNonVolatile() {
		return bundleList;
	}

	@Override
	public EntityBundle getItem(String dname) {
		return null;
	}

	@Override
	public List<EntityBundle> getSchemaList() {
		return schemaList;
	}

	@Override
	public List<CrossTable> getCrossList() {
		return crossList;
	}
}
